package fun.stgoder.dev.listener.sdk.dh;

import cn.hutool.core.thread.ThreadUtil;
import com.sun.jna.Native;
import com.sun.jna.Platform;
import com.sun.jna.Pointer;
import com.sun.jna.Structure;
import fun.stgoder.dev.listener.common.Constants;
import fun.stgoder.dev.listener.sdk.FaceMsg;
import fun.stgoder.dev.listener.sdk.FaceMsgExecutor;
import fun.stgoder.dev.listener.sdk.FaceMsgTask;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.concurrent.TimeUnit;

public class DhSDKWrapper {
    private Logger logger = LoggerFactory.getLogger(DhSDKWrapper.class);

    private NetSDKLib netsdk = DhSDKWrapper.load();

    private boolean initSuccess;
    private String ip;
    private Integer port;
    private String username;
    private String password;

    private NetSDKLib.NET_DEVICEINFO_Ex m_stDeviceInfo = new NetSDKLib.NET_DEVICEINFO_Ex();

    private NetSDKLib.LLong m_hLoginHandle = new NetSDKLib.LLong(0);

    private boolean bInit = false;
    private boolean bLogopen = false;

    private DisConnect disConnect = new DisConnect();

    private HaveReConnect haveReConnect = new HaveReConnect();

    private NetSDKLib.LLong m_hAttachHandle = new NetSDKLib.LLong(0);

    private NetSDKLib.fAnalyzerDataCallBack fAnalyzerDataCallBack;

    public DhSDKWrapper(String ip, Integer port, String username, String password) {
        this.ip = ip;
        this.port = port;
        this.username = username;
        this.password = password;
        this.fAnalyzerDataCallBack = new AnalyzerDataCB(ip);
    }

    /////////////////面板///////////////////
    // 设备断线回调: 通过 CLIENT_Init 设置该回调函数，当设备出现断线时，SDK会调用该函数
    public static class DisConnect implements NetSDKLib.fDisConnect {
        public void invoke(NetSDKLib.LLong m_hLoginHandle, String pchDVRIP, int nDVRPort, Pointer dwUser) {
            System.out.printf("Device[%s] Port[%d] DisConnect!\n", pchDVRIP, nDVRPort);
        }
    }

    // 网络连接恢复，设备重连成功回调
    // 通过 CLIENT_SetAutoReconnect 设置该回调函数，当已断线的设备重连成功时，SDK会调用该函数
    public static class HaveReConnect implements NetSDKLib.fHaveReConnect {
        private static Logger logger = LoggerFactory.getLogger(HaveReConnect.class);

        public void invoke(NetSDKLib.LLong m_hLoginHandle, String pchDVRIP, int nDVRPort, Pointer dwUser) {
            logger.error("ReConnect Device[%s] Port[%d]\n", pchDVRIP, nDVRPort);
        }
    }

    public static NetSDKLib load() {
        NetSDKLib netSDKLib = null;
        if (Platform.isWindows()) {
            netSDKLib = (NetSDKLib) Native.loadLibrary(Constants.DH_SDK_PATH, NetSDKLib.class);
        }
        if (Platform.isLinux()) {
            netSDKLib = (NetSDKLib) Native.loadLibrary(Constants.DH_SDK_PATH, NetSDKLib.class);
        }
        return netSDKLib;
    }

    public boolean init() {
        bInit = netsdk.CLIENT_Init(disConnect, null);
        if (!bInit) {
            logger.error("Initialize SDK failed");
            return false;
        }

        netsdk.CLIENT_SetAutoReconnect(haveReConnect, null);
        int waitTime = 5000; //登录请求响应超时时间设置为5S
        int tryTimes = 1;    //登录时尝试建立链接1次
        netsdk.CLIENT_SetConnectTime(waitTime, tryTimes);

        NetSDKLib.NET_PARAM netParam = new NetSDKLib.NET_PARAM();
        netParam.nConnectTime = 10000;      // 登录时尝试建立链接的超时时间
        netParam.nGetConnInfoTime = 3000;   // 设置子连接的超时时间
        netsdk.CLIENT_SetNetworkParam(netParam);

        return true;
    }

    public void cleanup() {
        if (bLogopen) {
            netsdk.CLIENT_LogClose();
        }

        if (bInit) {
            netsdk.CLIENT_Cleanup();
        }
    }

    //public boolean login(String m_strIp, int m_nPort, String m_strUser, String m_strPassword) {
    public boolean login() {
        //IntByReference nError = new IntByReference(0);
        //入参
        NetSDKLib.NET_IN_LOGIN_WITH_HIGHLEVEL_SECURITY pstInParam = new NetSDKLib.NET_IN_LOGIN_WITH_HIGHLEVEL_SECURITY();
        pstInParam.nPort = port;
        pstInParam.szIP = ip.getBytes();
        pstInParam.szPassword = password.getBytes();
        pstInParam.szUserName = username.getBytes();
        //出参
        NetSDKLib.NET_OUT_LOGIN_WITH_HIGHLEVEL_SECURITY pstOutParam = new NetSDKLib.NET_OUT_LOGIN_WITH_HIGHLEVEL_SECURITY();
        pstOutParam.stuDeviceInfo = m_stDeviceInfo;
        //m_hLoginHandle = netsdk.CLIENT_LoginEx2(m_strIp, m_nPort, m_strUser, m_strPassword, 0, null, m_stDeviceInfo, nError);
        m_hLoginHandle = netsdk.CLIENT_LoginWithHighLevelSecurity(pstInParam, pstOutParam);
        if (m_hLoginHandle.longValue() <= 0) {
            logger.error("Login Device[%s] Port[%d]Failed. %s\n", ip, port, getErrorCodePrint());
        } else {
            logger.info("Login Success [ " + ip + " ]");
            logger.info(getErrorCodePrint());
        }

        return m_hLoginHandle.longValue() == 0 ? false : true;
    }

    public String getErrorCodePrint() {
        return "\n{error code: (0x80000000|" + (netsdk.CLIENT_GetLastError() & 0x7fffffff) + ").参考  NetSDKLib.java }"
                + " - {error info:" + ErrorCode.getErrorCode(netsdk.CLIENT_GetLastError()) + "}\n";
    }

    public boolean logout() {
        if (m_hLoginHandle.longValue() == 0) {
            return false;
        }

        boolean bRet = netsdk.CLIENT_Logout(m_hLoginHandle);
        if (bRet) {
            m_hLoginHandle.setValue(0);
        }

        return bRet;
    }

    public boolean realLoadPicture(int channel) {
        int bNeedPicture = 1; // 是否需要图片

        m_hAttachHandle = netsdk.CLIENT_RealLoadPictureEx(m_hLoginHandle, channel,
                NetSDKLib.EVENT_IVS_ALL, bNeedPicture, fAnalyzerDataCallBack, null, null);
        if (m_hAttachHandle.longValue() <= 0) {
            logger.error("CLIENT_RealLoadPictureEx Failed, Error:" + getErrorCodePrint());
            return false;
        } else {
            logger.info("通道[" + channel + "]订阅成功！");
            return true;
        }
    }

    public void stopRealLoadPicture() {
        if (m_hAttachHandle.longValue() != 0) {
            netsdk.CLIENT_StopLoadPic(m_hAttachHandle);
            m_hAttachHandle.setValue(0);
        }
    }

    public static class AnalyzerDataCB implements NetSDKLib.fAnalyzerDataCallBack {
        private static Logger logger = LoggerFactory.getLogger(AnalyzerDataCB.class);

        private String ip;

        public AnalyzerDataCB(String ip) {
            this.ip = ip;
        }

        @Override
        public int invoke(NetSDKLib.LLong lAnalyzerHandle, int dwAlarmType, Pointer pAlarmInfo, Pointer pBuffer, int dwBufSize, Pointer dwUser, int nSequence, Pointer reserved) {
            if (lAnalyzerHandle.longValue() == 0 || pAlarmInfo == null) {
                return -1;
            }

            switch (dwAlarmType) {
                case NetSDKLib.EVENT_IVS_FACERECOGNITION:  ///< 人脸识别事件
                {
                    logger.info("EVENT_IVS_FACERECOGNITION");

                    // DEV_EVENT_FACERECOGNITION_INFO 结构体比较大，new对象会比较耗时， ToolKits.GetPointerData内容拷贝是不耗时的。
                    // 如果多台设备或者事件处理比较频繁，可以考虑将 static DEV_EVENT_FACERECOGNITION_INFO msg = new DEV_EVENT_FACERECOGNITION_INFO(); 改为全局。
                    // 写成全局，是因为每次new花费时间较多, 如果改为全局，此case下的处理需要加锁
                    // 加锁，是因为共用一个对象，防止数据出错

                    // 耗时800ms左右
                    NetSDKLib.DEV_EVENT_FACERECOGNITION_INFO msg = new NetSDKLib.DEV_EVENT_FACERECOGNITION_INFO();


                    // 耗时20ms左右
                    GetPointerData(pAlarmInfo, msg);
                    //msg.getPointer().write(0, pAlarmInfo.getByteArray(0, msg.size()), 0, msg.size());

                    // 保存图片，获取图片缓存
                    // 耗时20ms左右

                    // 释放内存
                    msg = null;
                    //System.gc();
                    break;
                }
                case NetSDKLib.EVENT_IVS_FACEDETECT:   ///< 人脸检测
                {
                    long makeMsg0Start = System.currentTimeMillis();
                    //logger.debug("EVENT_IVS_FACEDETECT");

                    NetSDKLib.DEV_EVENT_FACEDETECT_INFO msg = new NetSDKLib.DEV_EVENT_FACEDETECT_INFO();

                    GetPointerData(pAlarmInfo, msg);

                    // 保存图片，获取图片缓存
                    if (msg.stuObject.stPicInfo != null && msg.stuObject.stPicInfo.dwFileLenth > 0) {
                        byte[] smallFileBytes = pBuffer.getByteArray(msg.stuObject.stPicInfo.dwOffSet,
                                msg.stuObject.stPicInfo.dwFileLenth);

                        FaceMsg faceMsg = new FaceMsg();
                        faceMsg.setSmallFileBytes(smallFileBytes);
                        faceMsg.setTs(makeMsg0Start);
                        faceMsg.setCameraIp(ip);
                        faceMsg.setFaceScore(80);
                        faceMsg.setEts(System.currentTimeMillis());
                        FaceMsgExecutor.execute(new FaceMsgTask(faceMsg));
                    }
                    ///

                    // 释放内存
                    msg = null;
                    //System.gc();
                    break;
                }
                default:
                    break;
            }

            return 0;
        }
    }

    public boolean isInitSuccess() {
        return initSuccess;
    }

    public void setInitSuccess(boolean initSuccess) {
        this.initSuccess = initSuccess;
    }

    public String getIp() {
        return ip;
    }

    public void setIp(String ip) {
        this.ip = ip;
    }

    public Integer getPort() {
        return port;
    }

    public void setPort(Integer port) {
        this.port = port;
    }

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    public static void GetPointerData(Pointer pNativeData, Structure pJavaStu) {
        GetPointerDataToStruct(pNativeData, 0, pJavaStu);
    }

    public static void GetPointerDataToStruct(Pointer pNativeData, long OffsetOfpNativeData, Structure pJavaStu) {
        pJavaStu.write();
        Pointer pJavaMem = pJavaStu.getPointer();
        pJavaMem.write(0, pNativeData.getByteArray(OffsetOfpNativeData, pJavaStu.size()), 0,
                pJavaStu.size());
        pJavaStu.read();
    }

    public static void main(String[] args) {
        DhSDKWrapper sdkWrapper = new DhSDKWrapper("192.168.1.108", 37777, "admin", "a00000000");
        boolean ret = sdkWrapper.init();
        System.out.println(ret);
        ret = sdkWrapper.login();
        System.out.println(ret);
        ret = sdkWrapper.realLoadPicture(0);
        System.out.println(ret);

        ThreadUtil.sleep(1000, TimeUnit.SECONDS);
    }
}
